using System;
using System.Collections.Generic;
using System.Reflection;
using Bingo.API.LocalDB;
using Bingo.LocalDB.Entity;
using iBoxDB.LocalServer;
using QFramework;

namespace Bingo.LocalDB
{
    public class LocalDBSystem : ILocalDB, IDisposable
    {
        public const string DB_NAME = "game_db";

        private DB _db = null;
        private DB.AutoBox _autoBox = null; //该对象可以进行CRUD操作，使用完毕不需要释放对象

        public void Load()
        {
            DB.Root(UnityEngine.Application.persistentDataPath);
            if (this._db == null)
            {
                DB.Root(UnityEngine.Application.persistentDataPath); //数据库存储路径
                _db = new DB(2019); //数据库地址，或者说ID                     
                _db.GetConfig().EnsureTable<BaseObject>(DB_NAME, "ObjectName(20)"); //先建表后使用,并且表的主键为ObjectName，长度20

                InitDBTables();
            }

            if (_autoBox == null)
                _autoBox = _db.Open();
        }

        public void Dispose()
        {
            this._db.Close();
        }

        private void InitDBTables()
        {
            Assembly assembly = typeof(BaseEntity).Assembly;
            foreach (Type type in assembly.GetTypes())
            {
                object[] objects = type.GetCustomAttributes(typeof(BoEntityAttribute), false);
                if (objects.Length == 0)
                {
                    continue;
                }

//                Log.I("------------> init tables : " + type.Name);
                _db.GetConfig().EnsureTable(type.Name, "Id"); //先建表后使用,并且表的主键为ObjectName，长度20
            }
        }

        /// <summary>
        /// 删除数据库，IBoxDB中没有直接删除一个表的接口，所以这个方法显得格外亲切~
        /// 注意：删除数据库之前要关闭该数据库
        /// </summary>
        /// <param name="address">数据库地址</param>
        public void DeleteDataBase(int address)
        {
            iBoxDB.DBDebug.DDebug.DeleteDBFiles(address);
        }

        /// <summary>
        /// 存储一个列表的数据
        /// </summary>
        /// <param name="tableName">向哪个表存数据</param>
        /// <param name="data">要存储的数据集合</param>
        public void Save(string tableName, List<BaseObject> data)
        {
            IBox iBox = GetBox();
            iBoxDB.LocalServer.Binder binder = iBox.Bind(tableName); //绑定表

            foreach (BaseObject ob in data)
            {
                //如果表中之前有过该记录，则Update；没有则Insert
                if (_autoBox.SelectCount("from " + tableName + " where ObjectName == ?", ob.ObjectName) <= 0)
                    binder.Insert(ob);
                else
                    binder.Update(ob);
            }

            iBox.Commit();
            iBox.Dispose();
        }

        /// <summary>
        /// 存储一个对象的数据
        /// </summary>
        /// <param name="tableName">向哪个表存数据</param>
        /// <param name="ob">数据</param>
        public void Save(string tableName, BaseObject ob)
        {
            if (_autoBox.SelectCount("from " + tableName + " where ObjectName == ?", ob.ObjectName) <= 0)
                _autoBox.Insert(tableName, ob);
            else
                _autoBox.Update(tableName, ob);
        }

        /// <summary>
        /// 获取数据
        /// </summary>
        /// <param name="QL">QL语句</param>
        /// <param name="param">QL参数</param>
        /// <returns></returns>
        public List<BaseObject> Get(string QL, string param)
        {
            return _autoBox.Select<BaseObject>(QL, param);
        }

        public List<BaseObject> GetAll(string QL, string param)
        {
            return _autoBox.Select<BaseObject>(QL, param);
        }

        public BaseObject GetById(string id)
        {
            string sql = "from " + DB_NAME + " where ObjectName == ?";
            List<BaseObject> objects = Get(sql, id);
            if (objects.Count > 0)
            {
                return objects[0];
            }

            return null;
        }

        public void Save(string key, object value)
        {
            BaseObject item = new BaseObject {ObjectName = key.ToString()};
            item["Value"] = value;
            Save(LocalDBSystem.DB_NAME, item);
        }

        public K Get<K>(string key) where K : new()
        {
            BaseObject baseObject = GetById(key.ToString());

            if (baseObject != null)
            {
                return (K) baseObject["Value"];
            }

            return default(K);
        }

        public K Get<K>(string key, K @default) where K : new()
        {
            BaseObject baseObject = GetById(key);

            if (baseObject != null)
            {
                return (K) baseObject["Value"];
            }

            return @default;
        }

        public List<BaseObject> GetByType(string type)
        {
            string sql = "from " + DB_NAME + " where Type == ?";
            List<BaseObject> objects = Get(sql, type);
            return objects;
        }

        /// <summary>
        /// IBox可以进行数据库的事务操作
        /// </summary>
        private IBox GetBox()
        {
            return _autoBox.Cube();
        }

        /// <summary>
        /// 插入数据
        /// </summary>
        /// <param name="data"></param>
        /// <typeparam name="T"></typeparam>
        public void Insert<T>(T data) where T : BaseEntity
        {
            using (IBox box = this.GetBox())
            {
                string tableName = typeof(T).Name;
                box[tableName].Insert(data);
            }
        }

        public void Insert<T>(List<T> list) where T : BaseEntity
        {
            using (IBox box = this.GetBox())
            {
                string tableName = typeof(T).Name;
                foreach (T obj in list)
                {
                    box[tableName].Insert(obj);
                }
            }
        }

        public void Update<T>(T data) where T : BaseEntity
        {
            using (IBox box = this.GetBox())
            {
                string tableName = typeof(T).Name;
                box[tableName].Update(data);
            }
        }

        public void Delete<T>(long Id) where T : BaseEntity
        {
            string tableName = typeof(T).Name;
            this._autoBox.Delete(tableName, Id);
        }

        public List<T> GetAll<T>() where T : BaseEntity, new()
        {
            string tableName = typeof(T).Name;
            return this._autoBox.Select<T>("* from " + tableName);
        }

        public T GetById<T>(long Id) where T : BaseEntity, new()
        {
            string tableName = typeof(T).Name;
            using (IBox box = this.GetBox())
            {
                return box[tableName, Id].Select<T>();
            }
        }

        public T GetByParam<T>(string key, object value) where T : BaseEntity, new()
        {
            string tableName = typeof(T).Name;
            using (IBox box = this.GetBox())
            {
                return box[tableName, value, key].Select<T>();
            }
        }

        public bool Delete(string id)
        {
            return _autoBox.Delete("BaseObject", id);
        }

        public bool DeleteAll<T>() where T : BaseEntity
        {
            _autoBox.Delete(typeof(T).Name);
            return false;
        }
    }

    public class BaseObject : Dictionary<string, object>
    {
        public string ObjectName
        {
            get => (string) base["ObjectName"];
            set
            {
                if (value.Length > 20)
                {
                    throw new ArgumentOutOfRangeException();
                }

                base["ObjectName"] = value;
            }
        }

        public string Type
        {
            get => (string) base["Type"];
            set => base["Type"] = value;
        }
    }
}